use anyhow::{Context, Result};
use enumflags2::bitflags;
use ignore::{overrides::OverrideBuilder, WalkBuilder, WalkState};
use serde::Serialize;
use std::{
    fs,
    result::Result::Ok,
    sync::{Arc, Mutex},
};
use tokio::sync::mpsc;
use unity_yml_rust::{yaml::YamlLoader, Yaml};

use crate::RunInfo;

#[derive(Clone, Debug, Serialize)]
pub struct ResultData {
    pub asset_path: String,
    pub asset_bundle_name: String,
    pub prefix: String,
    pub asset_type: i64,
    pub build_type: BuildType,
}

#[derive(Copy, Clone, Debug, Serialize, PartialEq)]
pub enum BuildType {
    Single,
    Directory,
    Dependence,
    File,
    None,
}

impl From<i64> for BuildType {
    fn from(value: i64) -> Self {
        match value {
            0 => BuildType::Single,
            1 => BuildType::Directory,
            2 => BuildType::Dependence,
            3 => BuildType::File,
            _ => BuildType::None,
        }
    }
}

#[bitflags]
#[repr(u64)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum AssetType {
    Texture = 1 << 0,
    Material = 1 << 1,
    Prefab = 1 << 2,
    ScriptableObject = 1 << 3,
    Font = 1 << 4,
    SpriteAtlas = 1 << 5,
    Sprite = 1 << 6,
    Model = 1 << 7,
    AnimatorController = 1 << 8,
    Scene = 1 << 9,
    Shader = 1 << 10,
    Animation = 1 << 11,
    AvatarMask = 1 << 12,
    NavMeshData = 1 << 13,
    Mesh = 1 << 14,
}

impl AssetType {
    pub fn contain(self, flags: i64) -> bool {
        self as i64 & flags != 0
    }
}

pub async fn read_all_yaml(root_path: &str, enable_log: bool) -> Result<Vec<ResultData>> {
    let mut walk_builder: WalkBuilder = WalkBuilder::new(root_path);

    walk_builder.hidden(false);

    let mut override_builder = OverrideBuilder::new(root_path);
    override_builder.add("!**/.git").unwrap();
    override_builder.add("*.asset").unwrap();

    if let Ok(overrides) = override_builder.build() {
        walk_builder.overrides(overrides);
    }

    let mut paths = Vec::new();
    for result in walk_builder.build() {
        let Ok(entry) = result else {
            continue;
        };

        if !entry.path().is_file() {
            continue;
        }

        paths.push(String::from(entry.path().to_str().unwrap()));
    }

    let mut results: Vec<ResultData> = Vec::new();
    let (tx, mut rx) = mpsc::channel::<Option<Vec<ResultData>>>(paths.len());
    for path in paths.iter() {
        let async_path = path.clone();
        let tx_clone: mpsc::Sender<Option<Vec<ResultData>>> = tx.clone();
        tokio::spawn(async move {
            let result;
            match read_yaml(&async_path) {
                Ok(ret) => {
                    if ret.is_empty() {
                        result = None;
                    } else {
                        result = Some(ret);
                    }
                }
                Err(e) => {
                    eprintln!("Error : {:?}", e);
                    result = None;
                }
            }

            if tx_clone.send(result).await.is_err() {
                eprintln!("    Error[prefab_text] : read_all_yaml send error");
            }
        });
    }

    drop(tx);

    while let Some(result) = rx.recv().await.as_mut() {
        if let Some(result) = result {
            results.append(result);
        }
    }

    if enable_log {
        for result in results.iter() {
            println!(
                "{{ \"asset_path\" : \"{}\", \"asset_bundle_name\" : \"{}\", \"asset_type\" : \"{}\", \"build_type\" : \"{:?}\" }}",
                result.asset_path, result.asset_bundle_name, result.asset_type, result.build_type
            );
        }
    }

    anyhow::Ok(results)
}

pub fn read_yaml(path: &str) -> Result<Vec<ResultData>> {
    let content =
        fs::read_to_string(path).with_context(|| format!("Error : 文件读取失败 - {}", path))?;

    let mut docs = YamlLoader::load_from_str(&content)
        .with_context(|| format!("Error : Yaml 解析失败 - {}", path))?;

    let mut results = Vec::new();

    // Multi document support, doc is a yaml::Yaml
    for doc in docs.iter_mut() {
        if matches!(doc, Yaml::Original(_)) {
            continue;
        }

        if matches!(doc["MonoBehaviour"], Yaml::BadValue | Yaml::Null) {
            continue;
        }

        let mono = &mut doc["MonoBehaviour"];
        if matches!(mono["assetBundleRules"], Yaml::BadValue | Yaml::Null) {
            continue;
        }

        let Some(rules) = mono["assetBundleRules"].as_vec() else {
            continue;
        };

        for rule in rules.iter() {
            let Some(build_type) = rule["buildType"].as_i64() else {
                continue;
            };

            // BuildType::Dependence BuildType::None 不进行构建
            if build_type == 2 || build_type == 4 {
                continue;
            }

            let Some(asset_path) = rule["assetPath"].as_str() else {
                continue;
            };
            let Some(asset_type) = rule["assetType"].as_i64() else {
                continue;
            };

            let asset_bundle_name = rule["assetBundleName"].as_str().or(Some("")).unwrap();
            let prefix = rule["prefix"].as_str().or(Some("")).unwrap();
            results.push(ResultData {
                asset_path: String::from(asset_path),
                asset_bundle_name: String::from(asset_bundle_name),
                asset_type: asset_type,
                build_type: BuildType::from(build_type),
                prefix: String::from(prefix),
            })
        }
    }

    anyhow::Ok(results)
}

pub async fn run_vec_async(
    source: &str,
    target: &[String],
    sender: std::sync::mpsc::Sender<RunInfo>,
) -> anyhow::Result<Vec<String>> {
    let path = format!(
        "{}/Assets/Script/Editor/BundleBuild/Editor/ScriptableObjects",
        source
    );
    let results = read_all_yaml(&path, false)
        .await
        .with_context(|| format!("Error : 文件读取失败 - {}", path))?;

    let mut print_ret = vec![];
    for result in results {
        let source_path = format!("{}/{}", source, result.asset_path);
        let mut walk_builder: WalkBuilder = WalkBuilder::new(source_path);
        let dents = Arc::new(Mutex::new(vec![]));
        walk_builder.add_ignore("**.meta");
        walk_builder.build_parallel().run(|| {
            let build_type = result.build_type.clone();
            let asset_bundle_name = result.asset_bundle_name.clone();
            let asset_prefix = result.prefix.clone();
            let asset_path = result.asset_path.clone();
            let dents = dents.clone();
            let sender_c = sender.clone();

            Box::new(move |result| {
                let Ok(dent) = result else {
                    return WalkState::Quit;
                };

                if let Some(ext) = dent.path().extension() {
                    if ext == "meta" {
                        return WalkState::Continue;
                    }
                }

                let _ = sender_c.send(RunInfo {
                    running: dent.path().to_str().unwrap().to_string(),
                });
                match build_type {
                    BuildType::File => {
                        if dent.path().is_file() {
                            if target.iter().any(|val| {
                                val.as_str() == dent.path().file_stem().unwrap().to_str().unwrap()
                            }) {
                                dents
                                    .lock()
                                    .unwrap()
                                    .push(format!("{}.unity3d", asset_bundle_name.to_lowercase()));
                            }
                        }
                    }
                    BuildType::Single => {
                        if dent.path().is_file() {
                            if target.iter().any(|val| {
                                val.as_str() == dent.path().file_stem().unwrap().to_str().unwrap()
                            }) {
                                let mut bundle_name = dent
                                    .path()
                                    .with_extension("")
                                    .strip_prefix(format!("{}/{}", source, asset_path))
                                    .unwrap()
                                    .to_str()
                                    .unwrap()
                                    .replace("\\", "/")
                                    .to_lowercase();

                                if !asset_prefix.is_empty() {
                                    bundle_name = format!("{}/{}", asset_prefix, bundle_name);
                                }

                                dents
                                    .lock()
                                    .unwrap()
                                    .push(format!("{}.unity3d", bundle_name,));
                            }
                        }
                    }
                    BuildType::Directory => {
                        if dent.path().is_dir() {
                            if target
                                .iter()
                                .any(|val| val.as_str() == dent.file_name().to_str().unwrap())
                            {
                                let mut bundle_name = dent
                                    .path()
                                    .strip_prefix(format!("{}/{}", source, asset_path))
                                    .unwrap()
                                    .to_str()
                                    .unwrap()
                                    .replace("\\", "/")
                                    .to_lowercase();

                                if !asset_prefix.is_empty() {
                                    bundle_name = format!(
                                        "{}/{}",
                                        asset_prefix.trim_end_matches('/'),
                                        bundle_name
                                    );
                                }

                                dents
                                    .lock()
                                    .unwrap()
                                    .push(format!("{}.unity3d", bundle_name.trim_end_matches('/')));
                            }
                        }
                    }
                    _ => {}
                }

                WalkState::Continue
            })
        });

        let dents = dents.lock().unwrap();
        print_ret.append(dents.to_vec().as_mut());
    }

    print_ret.sort();
    print_ret.dedup();
    for ret in print_ret.clone() {
        if ret.starts_with("share/") {
            continue;
        }

        print!("{}\n", ret);
    }

    anyhow::Ok(print_ret)
}
